﻿namespace MapleRock.P2PEmulator.Core
{
  using System;
  using System.Collections.Generic;
  using System.Diagnostics;
  using System.Linq;
  using System.Net;
  using System.Text;

  public static class CitiesGenerator
  {
    public static void Generate(XmlInputData data, ref Dictionary<uint, City> cities)
    {
      Debug.Assert(null != data, "null != data");
      Debug.Assert(null != cities, "null != cities");
      Debug.Assert(0 == cities.Count, "0 == cities.Count");

      var minLatitude = 0;
      var maxLatitude = data.Geography[0].AreaLatitude;
      Debug.Assert(maxLatitude > 0, "maxLatitude > 0");

      var minLongitude = 0;
      var maxLongitude = data.Geography[0].AreaLongitude;
      Debug.Assert(maxLongitude > 0, "maxLongitude > 0");

      var citySizeMin = data.Geography[0].CitySizeMin;
      Debug.Assert(citySizeMin > 0, "citySizeMin > 0");

      var citySizeMax = data.Geography[0].CitySizeMax;
      Debug.Assert(citySizeMax > 0, "citySizeMax > 0");

      Debug.Assert(citySizeMin <= citySizeMax, "citySizeMin < citySizeMax");

      var gridCellSize = citySizeMax;

      var citiesGridSizeX = (int)((maxLatitude - minLatitude) / gridCellSize);
      Debug.Assert(citiesGridSizeX > 0, "citiesGridSizeX > 0");
      
      var citiesGridSizeY = (int)((maxLongitude - minLongitude) / gridCellSize);
      Debug.Assert(citiesGridSizeY > 0, "citiesGridSizeY > 0");
      
      var gridCellsCount = citiesGridSizeX * citiesGridSizeY;
      Debug.Assert(
        gridCellsCount >= data.Geography[0].CitiesNumber, 
        "gridCellsCount >= data.Geography[0].CitiesNumber");
      Debug.Assert(
        data.PeersNumber > data.Geography[0].CitiesNumber,
        "data.PeersNumber > data.Geography[0].CitiesNumber");

      if (gridCellsCount < data.Geography[0].CitiesNumber)
      {
        throw new InvalidOperationException("Too little place for cities.");
      }

      var availableGridRects = new List<int>();
      for (var i = 0; i < gridCellsCount; ++i)
      {
        availableGridRects.Add(i);
      }

      var totalCitiesRadius = 0.0;

      var randomGenerator = new Random();
      Debug.Assert(data.Geography[0].CitiesNumber > 0, "data.Geography[0].CitiesNumber > 0");
      for (var i = 1u; i <= data.Geography[0].CitiesNumber; ++i)
      {
        Debug.Assert(0 != availableGridRects.Count, "0 != availableGridRects.Count");

        var cellNumberIndex = randomGenerator.Next(availableGridRects.Count);
        var cellNumber = availableGridRects[cellNumberIndex];
        availableGridRects.RemoveAt(cellNumberIndex);

        var cellLatitude = ((cellNumber % citiesGridSizeX) * gridCellSize);
        var cellLongitude = ((cellNumber / citiesGridSizeX) * gridCellSize);

        var randDouble = randomGenerator.NextDouble();
        var nextRadius = (citySizeMin + ((citySizeMax - citySizeMin) * randDouble));
        nextRadius /= 2.0;

        totalCitiesRadius += nextRadius;

        var positionDelta = (citySizeMax - (nextRadius * 2.0));
        Debug.Assert(positionDelta >= 0, "positionDelta >= 0");

        randDouble = randomGenerator.NextDouble();
        var nextLatitudeDelta = nextRadius + (randDouble * positionDelta);

        randDouble = randomGenerator.NextDouble();
        var nextLongitudeDelta = nextRadius + (randDouble * positionDelta);

        var nextLatitude = cellLatitude + nextLatitudeDelta;
        Debug.Assert(nextLatitude >= nextRadius, "nextLatitude >= nextRadius");
        Debug.Assert(
          (nextLatitude + nextRadius) <= maxLatitude,
          "(nextLatitude + nextRadius) <= maxLatitude");

        var nextLongitude = cellLongitude + nextLongitudeDelta;
        Debug.Assert(nextLongitude >= nextRadius, "nextLongitude >= nextRadius");
        Debug.Assert(
          (nextLongitude + nextRadius) <= maxLongitude,
          "(nextLongitude + nextRadius) <= maxLongitude");

        var city = new City(i, nextLatitude, nextLongitude, nextRadius);

        cities.Add(i, city);
      }

      var peersLeft = data.PeersNumber;
      for (var i = 0; i < cities.Count; ++i)
      {
        Debug.Assert(peersLeft > 0, "peersLeft > 0");

        var city = cities.Values.ElementAt(i);

        var nextSize = Convert.ToInt32(Math.Floor(
          (city.Radius / totalCitiesRadius) * data.PeersNumber));
        if (i == (cities.Count - 1))
        {
          nextSize = peersLeft;
        }

        city.InitSize(nextSize);

        peersLeft -= nextSize;
      }
    }
  }
}
